﻿using System;
using System.Collections.Generic;
using System.Threading;
using Cjc.Calculation.ExpressionEngine.Excel.Compiler;
using Cjc.Calculation.ExpressionEngine.Excel.Runtime;
using Cjc.Calculation.ExpressionEngine.Excel.Runtime.Binding;
using System.Linq.Expressions;
using System.Dynamic;
using Microsoft.Scripting;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Runtime;

namespace Cjc.Calculation.ExpressionEngine.Excel
{
	public interface ICompileExpression
	{
		Expression CompileExpression( string expressionText, Type resultType );
	}

	public class ExcelContext : LanguageContext, ICompileExpression
	{
		private static ExcelBinder binder;

        public ExcelBinder Binder { get { return binder; } private set { binder = value; } }

		/// <summary>
		/// Initializes a new instance of the <see cref="ExcelContext"/> class.
		/// </summary>
		/// <param name="manager">The manager.</param>
		/// <param name="options">The options.</param>
		public ExcelContext( ScriptDomainManager manager, IDictionary<string, object> options )
			: base( manager )
		{
			if ( binder == null ) Interlocked.CompareExchange<ExcelBinder>( ref binder, new ExcelBinder( ), null );
			Binder = binder;
		}

		/// <summary>
		/// Parses the source code within a specified compiler context.
		/// The source unit to parse is held on by the context.
		/// </summary>
		/// <param name="sourceUnit"></param>
		/// <param name="options"></param>
		/// <param name="errorSink"></param>
		/// <returns><b>null</b> on failure.</returns>
		/// <remarks>Could also set the code properties and line/file mappings on the source unit.</remarks>
		public override ScriptCode CompileSourceCode( SourceUnit sourceUnit, CompilerOptions options, ErrorSink errorSink )
		{
			Expression body = null;

			switch ( sourceUnit.Kind )
			{
				case SourceCodeKind.AutoDetect:
				case SourceCodeKind.Expression:
					{
						body = CompileExpression( sourceUnit.GetCode(), null );
						break;
					}
			}

			if ( body == null ) return null;

			var builder = Utils.Lambda( typeof( object ), "Excel" );
			builder.Body = Utils.Convert( body, typeof( object ) );

            return new ExcelScriptCode(builder.MakeLambda(), sourceUnit);
		}

		/// <summary>
		/// Compiles the expression.
		/// </summary>
		/// <param name="expressionText">The expression text.</param>
		/// <param name="resultType">The expression result type.</param>
		/// <returns>Returns an expression tree representing the expression text.</returns>
		public Expression CompileExpression( string expressionText, Type resultType )
		{
			var scanner = new Scanner();
			var parser = new Parser( scanner );
			var tree = parser.Parse( expressionText );

			return ( tree != null )
				? tree.GetExpression( this )
				: null;
		}

		public override InvokeMemberBinder CreateCallBinder( string name, bool ignoreCase, CallInfo callInfo )
		{
			return new ExcelCallAction<ExcelHelpers>( name, ignoreCase, callInfo );
		}

		public override BinaryOperationBinder CreateBinaryOperationBinder( ExpressionType operation )
		{
            return new ExcelOperationBinder(binder, operation);
        }

		public override UnaryOperationBinder CreateUnaryOperationBinder( ExpressionType operation )
		{
            return null; // new ExcelOperationBinder(binder, operation);
        }

        //public override OperationBinder CreateOperationBinder( string operation )
        //{
            //return null; // new ExcelOperationBinder(binder, operation);
        //}
	}
}